#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "freertos/event_groups.h"
#include "esp_err.h"

#include "mqtt_client.h"
#include "esp_log.h"

#include "mqttc.h"

#define TAG "mqttc"

enum
{
    MQTTC_STA_DISCONNECTED,
    MQTTC_STA_CONNECTED,
    MQTTC_STA_CONNECTING,
};

#ifndef BITS
#define BITS(s,e)   (((uint32_t)-1 << (s)) & ((uint32_t)-1 >> (31 - (e))))
#endif

#define MQTTC_EVT_FLAG_CONNECTED        BIT0
#define MQTTC_EVT_FLAG_DISCONNECTED     BIT1
#define MQTTC_EVT_FLAG_SUBSCRIBED       BIT2
#define MQTTC_EVT_FLAG_UNSUBSCRIBED     BIT3
#define MQTTC_EVT_FLAG_PUBLISHED        BIT4
#define MQTTC_EVT_FLAG_START            BIT5
#define MQTTC_EVT_FLAG_STOP             BIT6
#define MQTTC_EVT_FLAG_ALL_BITS         BITS(0, 6)
#define MQTTC_EVT_FLAG_IS_SET(evt, bits)    (((evt) & (bits)) == (bits))


static void log_error_if_nonzero(const char *message, int error_code)
{
    if (error_code != 0) {
        ESP_LOGE(TAG, "Last error %s: 0x%x", message, error_code);
    }
}
static void mqttc_event_handler(void *handler_args, esp_event_base_t base, int32_t event_id, void *event_data)
{
    ESP_LOGD(TAG, "Event dispatched from event loop base=%s, event_id=%" PRIi32 "", base, event_id);
    struct mqttc_handler *handler = (struct mqttc_handler *)handler_args;
    esp_mqtt_event_handle_t event = event_data;
    esp_mqtt_client_handle_t client = event->client;
    int msg_id;
    switch ((esp_mqtt_event_id_t)event_id) {
    case MQTT_EVENT_CONNECTED:
        handler->state = MQTTC_STA_CONNECTED;
        xEventGroupSetBits(handler->event_handler, MQTTC_EVT_FLAG_CONNECTED);
        ESP_LOGI(TAG, "MQTT_EVENT_CONNECTED");
        break;
    case MQTT_EVENT_DISCONNECTED:
        handler->state = MQTTC_STA_CONNECTING; //MQTTC_STA_DISCONNECTED; //
        xEventGroupSetBits(handler->event_handler, MQTTC_EVT_FLAG_DISCONNECTED);
        ESP_LOGI(TAG, "MQTT_EVENT_DISCONNECTED");
        break;
    case MQTT_EVENT_SUBSCRIBED:
        ESP_LOGI(TAG, "MQTT_EVENT_SUBSCRIBED, msg_id=%d", event->msg_id);
        xEventGroupSetBits(handler->event_handler, MQTTC_EVT_FLAG_SUBSCRIBED);
        break;
    case MQTT_EVENT_UNSUBSCRIBED:
        ESP_LOGI(TAG, "MQTT_EVENT_UNSUBSCRIBED, msg_id=%d", event->msg_id);
        xEventGroupSetBits(handler->event_handler, MQTTC_EVT_FLAG_UNSUBSCRIBED);
        break;
    case MQTT_EVENT_PUBLISHED:
        ESP_LOGI(TAG, "MQTT_EVENT_PUBLISHED, msg_id=%d", event->msg_id);
        xEventGroupSetBits(handler->event_handler, MQTTC_EVT_FLAG_PUBLISHED);
        break;
    case MQTT_EVENT_DATA:
        ESP_LOGI(TAG, "MQTT_EVENT_DATA");
        // printf("TOPIC=%.*s\r\n", event->topic_len, event->topic);
        // printf("DATA=%.*s\r\n", event->data_len, event->data);
        if(handler->data_handler)
        {
            handler->data_handler(handler, event->topic, event->topic_len, (void *)event->data, event->data_len);
        }
        break;
    case MQTT_EVENT_ERROR:
        ESP_LOGI(TAG, "MQTT_EVENT_ERROR");
        if (event->error_handle->error_type == MQTT_ERROR_TYPE_TCP_TRANSPORT) {
            log_error_if_nonzero("reported from esp-tls", event->error_handle->esp_tls_last_esp_err);
            log_error_if_nonzero("reported from tls stack", event->error_handle->esp_tls_stack_err);
            log_error_if_nonzero("captured as transport's socket errno",  event->error_handle->esp_transport_sock_errno);
            ESP_LOGI(TAG, "Last errno string (%s)", strerror(event->error_handle->esp_transport_sock_errno));
        }
        break;
    default:
        ESP_LOGI(TAG, "Other event id:%d", event->event_id);
        break;
    }
}

static void mqttc_process_entry(void *p)
{
    struct mqttc_handler *handler = p;
    esp_mqtt_client_handle_t client = NULL;
    EventGroupHandle_t event = NULL;
    uint32_t evt;
    if(handler == NULL)
    {
        ESP_ERROR_CHECK(ESP_FAIL);
    }
    client = handler->client_handler;
    if(client == NULL)
    {
        ESP_ERROR_CHECK(ESP_FAIL);
    }
    event = handler->event_handler;
    if(event == NULL)
    {
        ESP_ERROR_CHECK(ESP_FAIL);
    }
    ESP_LOGI(TAG, "mqttc run...%p", event);
    while(1)
    {
        evt = xEventGroupWaitBits(event, MQTTC_EVT_FLAG_ALL_BITS, pdTRUE, pdFALSE, portMAX_DELAY);
        ESP_LOGI(TAG, "evt:%x", evt);
        if(evt & BIT31)
        {
            ESP_LOGE(TAG, "wait bits timeout");
            continue;
        }
        if(MQTTC_EVT_FLAG_IS_SET(evt, MQTTC_EVT_FLAG_CONNECTED))
        {
            int msg_id;
            msg_id = esp_mqtt_client_subscribe(client, D2C_HEATBEAT, 0);
            ESP_LOGI(TAG, "msg_id=%d", msg_id);
        }
        if(MQTTC_EVT_FLAG_IS_SET(evt, MQTTC_EVT_FLAG_DISCONNECTED))
        {
            ESP_LOGI(TAG, "mqtt disconned.");
        }
        if(MQTTC_EVT_FLAG_IS_SET(evt, MQTTC_EVT_FLAG_START))
        {
            if(handler->state == MQTTC_STA_DISCONNECTED)
            {
                ESP_LOGI(TAG, "mqtt client start:");
                esp_err_t err = esp_mqtt_client_start(client);
                ESP_LOGI(TAG, "%d", err);
                handler->state = MQTTC_STA_CONNECTING;
            }
        }
        if(MQTTC_EVT_FLAG_IS_SET(evt, MQTTC_EVT_FLAG_STOP))
        {
            ESP_LOGI(TAG, "mqtt client stop:");
            esp_err_t err = esp_mqtt_client_stop(client);
            ESP_LOGI(TAG, "%d", err);
        }
    }
}

void mqttc_init(struct mqttc_handler *handler, mqttc_data_arrived_cb_t data_handler, int priority)
{
    esp_mqtt_client_config_t mqtt_cfg = {
        .broker.address.uri = "mqtt://192.168.4.100",
    };
    esp_mqtt_client_handle_t mqttc;
    if(handler == NULL)
    {
        ESP_ERROR_CHECK(ESP_FAIL);
    }
    if((handler->event_handler != NULL)
        && (handler->client_handler != NULL)
        && (handler->task_handler != NULL))
    {
        return ;
    }
    mqttc = esp_mqtt_client_init(&mqtt_cfg);
    if(mqttc == NULL)
    {
        ESP_ERROR_CHECK(ESP_FAIL);
    }
    esp_mqtt_client_register_event(mqttc, ESP_EVENT_ANY_ID, mqttc_event_handler, handler);
    handler->client_handler = (void *)mqttc;
    handler->event_handler = xEventGroupCreate();
    if(handler->client_handler == NULL || handler->event_handler == NULL)
    {
        ESP_ERROR_CHECK(ESP_FAIL);
    }
    if(pdPASS != xTaskCreate(mqttc_process_entry, "mqttc_process", 8192, handler, priority, (TaskHandle_t *)&(handler->task_handler)))
    {
        ESP_ERROR_CHECK(ESP_FAIL);
    }
    handler->data_handler = data_handler;
    handler->state = MQTTC_STA_DISCONNECTED;
    ESP_LOGI(TAG, "%p,%p,%p", handler->client_handler, handler->event_handler, handler->task_handler);
}
int mqttc_is_connected(struct mqttc_handler *handler)
{
    if(handler->state == MQTTC_STA_CONNECTED)
    {
        return true;
    }
    return false;
}
int mqttc_is_disconnected(struct mqttc_handler *handler)
{
    if(handler->state == MQTTC_STA_DISCONNECTED)
    {
        return true;
    }
    return false;
}
int mqttc_start(struct mqttc_handler *handler)
{
    if(handler == NULL)
    {
        ESP_LOGE(TAG, "handler is NULL");
        return ESP_FAIL;
    }
    if (handler->event_handler == NULL)
    {
        ESP_LOGE(TAG, "event_handler is NULL");
        return ESP_FAIL;
    }
    ESP_LOGI(TAG, "event:%p, bits:%p", handler->event_handler, MQTTC_EVT_FLAG_START);
    xEventGroupSetBits(handler->event_handler, MQTTC_EVT_FLAG_START);
    return ESP_OK;
}
int mqttc_stop(struct mqttc_handler *handler)
{
    if(handler == NULL)
    {
        return ESP_FAIL;
    }
    if (handler->event_handler == NULL)
    {
        return ESP_FAIL;
    }
    ESP_LOGI(TAG, "event:%p, bits:%p", handler->event_handler, MQTTC_EVT_FLAG_STOP);
    xEventGroupSetBits(handler->event_handler, MQTTC_EVT_FLAG_STOP);
    return ESP_OK;
}

int mqttc_device_subscribe(struct mqttc_handler *handler, const char *device)
{
    char topic_str[128];
    // esp_err_t err;
    // uint8_t mac[8];
    // memset(mac, 0, sizeof(mac));
    // err = esp_efuse_mac_get_default(mac);
    if(handler == NULL)
    {
        ESP_LOGW(TAG, "handler is NULL");
        return ESP_FAIL;
    }
    if (handler->client_handler == NULL)
    {
        ESP_LOGW(TAG, "client handler is NULL");
        return ESP_FAIL;
    }
    if(!mqttc_is_connected(handler))
    {
        ESP_LOGW(TAG, "mqttc isn't ready");
        return ESP_FAIL;
    }
    if((device == NULL) || strlen(device) > 16)
    {
        ESP_LOGW(TAG, "device is invalid");
        return ESP_FAIL;
    }
    snprintf(topic_str, sizeof(topic_str), D2C_RESPONSE, device);
    ESP_LOGI(TAG, "sent subscribe %s", topic_str);
    int msg_id = esp_mqtt_client_subscribe(handler->client_handler, topic_str, 0);
    ESP_LOGI(TAG, "msg_id=%d", msg_id);
    return ESP_OK;
}
int mqttc_device_unsubscribe(struct mqttc_handler *handler, const char *device)
{
    char topic_str[128];
    if(handler == NULL)
    {
        return ESP_FAIL;
    }
    if (handler->client_handler == NULL)
    {
        return ESP_FAIL;
    }
    if(!mqttc_is_connected(handler))
    {
        return ESP_FAIL;
    }
    if((device == NULL) || strlen(device) > 16)
    {
        return ESP_FAIL;
    }
    snprintf(topic_str, sizeof(topic_str), D2C_RESPONSE, device);
    ESP_LOGI(TAG, "sent subscribe %s", topic_str);
    esp_mqtt_client_unsubscribe(handler->client_handler, topic_str);
    return ESP_OK;
}
static int _mqttc_device_publish(struct mqttc_handler *handler, const char *topic, const char *data)
{
    if(handler == NULL)
    {
        ESP_LOGW(TAG, "handler is NULL");
        return ESP_FAIL;
    }
    if (handler->client_handler == NULL)
    {
        ESP_LOGW(TAG, "client handler is NULL");
        return ESP_FAIL;
    }
    if(!mqttc_is_connected(handler))
    {
        ESP_LOGW(TAG, "mqttc isn't ready");
        return ESP_FAIL;
    }
    if(topic == NULL)
    {
        ESP_LOGW(TAG, "topic is invalid");
        return ESP_FAIL;
    }
    int msgid = esp_mqtt_client_publish(handler->client_handler, topic, data, 0, 0, 0);
    ESP_LOGI(TAG, "sent publish, topic=%s, msgid=%d, data=%s", topic, msgid, data);
    return ESP_OK;
}
int mqttc_device_publish_request(struct mqttc_handler *handler, const char *device, const char *data)
{
    char topic_str[128];
    if((device == NULL) || (strlen(device) > 16))
    {
        return ESP_FAIL;
    }
    snprintf(topic_str, sizeof(topic_str), C2D_REQUEST, device);
    return _mqttc_device_publish(handler, topic_str, data);
}
int mqttc_device_publish_led_req(struct mqttc_handler *handler, const char *device, int led)
{
    char buf[128];
    int len = snprintf(buf, sizeof(buf), C2D_REQUEST, device);
    buf[len++] = '\0';
    snprintf(buf + len, sizeof(buf) - len, "{\"led\":%d}", led);
    return _mqttc_device_publish(handler, buf, &buf[len]);
}
int mqttc_device_publish_data(struct mqttc_handler *handler, const char *data)
{
    return _mqttc_device_publish(handler, D2C_HEATBEAT, data);
}